
#include "SimplygonBlueprintLibrary.h"
#include "LODRecipeManager.h"
#include "StandinUtilitiesModule.h"
#include "LODRecipe.h"

#include "Engine/StaticMeshActor.h"

DEFINE_LOG_CATEGORY_STATIC( LogSimplygonBlueprintLibrary, Verbose, All );
#define LOCTEXT_NAMESPACE "SimplygonBlueprintLibrary"

static bool ContainsMetaTag( AActor* InActor, ESimplygonMetaTagType InMetaTag )
{
	
	TInlineComponentArray<UStaticMeshComponent*> Components;
	InActor->GetComponents( Components );

	for( auto& Component : Components )
	{
		USimplygonAssetUserData* SimplygonUserData = Component->GetAssetUserData<USimplygonAssetUserData>();
		if( SimplygonUserData )
		{
			if( SimplygonUserData->MetaTag == InMetaTag )
			{
				return true;
			}
		}
	}
	return false;
}

USimplygonBPFunctionLibrary::USimplygonBPFunctionLibrary( const FObjectInitializer& ObjectInitializer )
	: Super(ObjectInitializer)
{ }


bool USimplygonBPFunctionLibrary::BuildStandinMeshes()
{
	IStandinUtilitiesModule::Get().BuildStandinMeshActors(false);
	return false;
}


AStandInMeshActor* USimplygonBPFunctionLibrary::CreateStandinActorFromActors( const TArray<AActor*>& InActors )
{
	
	AStandInMeshActor* Result = IStandinUtilitiesModule::Get().CreateStandinMeshActor(InActors);

	return Result;
}


void USimplygonBPFunctionLibrary::AddActorToStandinActor( AStandInMeshActor* InStandin, AActor* InActor )
{
	if( !InStandin || !InActor )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InStandin or InActor was null." ));
		return;
	}

	IStandinUtilitiesModule::Get().AddActorToStandinActor(InActor, InStandin);
}


void USimplygonBPFunctionLibrary::AddActorsToStandinActor( AStandInMeshActor* InStandin, const TArray<AActor*>& InActors )
{
	if( !InStandin )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InStandin was null." ) );
		return;
	}


	IStandinUtilitiesModule::Get().AddActorsToStandinActor(InStandin, InActors);
}


void USimplygonBPFunctionLibrary::RemoveActorFromStandinActor( AActor* InActor )
{
	if( !InActor )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InActor was null." ) );
		return;
	}

	IStandinUtilitiesModule::Get().RemoveActorFromStandinActor(InActor);
}

void USimplygonBPFunctionLibrary::RemoveActorsFromStandinActor( AStandInMeshActor* InStandin, const TArray<AActor*>& InActors )
{
	if( !InStandin )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InStandin was null." ) );
		return;
	}
		
	IStandinUtilitiesModule::Get().RemoveActorsFromStandinActor(InStandin, InActors);
}



void USimplygonBPFunctionLibrary::BuildAllLODRecipes()
{
	FLODRecipeManager::Get().BuildAll(  );
}

void USimplygonBPFunctionLibrary::BuildLODRecipe( TSoftObjectPtr<UObject> Mesh )
{
	if( !Mesh.IsValid() )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "Mesh not a valid pointer." ) );
		return;
	}

	FLODRecipeManager::Get().Build( Mesh );
}


void USimplygonBPFunctionLibrary::BuildLODRecipesOnlyForStaticMeshes( const TArray<class ULODRecipe*>& LODRecipes )
{
	FLODRecipeContext InContext;
	InContext.FilterCriteria = ELODRecipeFilterCriteria::StaticMeshes;
	FLODRecipeManager::Get().Build( LODRecipes, InContext );
}

void USimplygonBPFunctionLibrary::BuildLODRecipesOnlyForSkeletalMeshes( const TArray<class ULODRecipe*>& LODRecipes )
{
	FLODRecipeContext InContext;
	InContext.FilterCriteria = ELODRecipeFilterCriteria::SkeletalMeshes;
	FLODRecipeManager::Get().Build( LODRecipes, InContext );
}

void USimplygonBPFunctionLibrary::AssignRecipeToAsset( ULODRecipe* InRecipe, UObject* InAsset )
{
	if( !InRecipe || !InAsset )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InRecipe or InAsset was null." ) );
		return;
	}

	InRecipe->AddMesh( InAsset );
}


bool USimplygonBPFunctionLibrary::IsClippingGeometry( AActor* InActor )
{
	if( !InActor || ! InActor->IsA<AStaticMeshActor>() )
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InActor was null or not a valid StaticMeshActor." ) );
		return false;
	}
		

	return ContainsMetaTag( InActor, ESimplygonMetaTagType::ClippingGeometry );

}

bool USimplygonBPFunctionLibrary::IsOccludingGeometry( AActor* InActor )
{
	if (!InActor || !InActor->IsA<AStaticMeshActor>()) {

		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InActor was null or not a valid StaticMeshActor." ) );
		return false;
	}
		

	return ContainsMetaTag( InActor, ESimplygonMetaTagType::Occluder );
}


void USimplygonBPFunctionLibrary::AddSimplygonUserData( AActor* InActor, ESimplygonMetaTagType InMetaTag )
{
	if (!InActor) 
	{
		UE_LOG( LogSimplygonBlueprintLibrary, Warning, TEXT( "InActor was null" ) );
		return;
	}

	TInlineComponentArray<UStaticMeshComponent*> Components;
	InActor->GetComponents( Components );

	for( auto& Component : Components ) 
	{
		USimplygonAssetUserData* SimplygonUserData = Component->GetAssetUserData<USimplygonAssetUserData>();
		if( !SimplygonUserData )
		{
			SimplygonUserData = NewObject<USimplygonAssetUserData>( Component, USimplygonAssetUserData::StaticClass() );
			SimplygonUserData->SetFlags( RF_Transactional );
			SimplygonUserData->MetaTag = InMetaTag;
			InActor->GetRootComponent()->AddAssetUserData( SimplygonUserData );
			Component->AddAssetUserData( SimplygonUserData );
		}
		else
		{
			SimplygonUserData->MetaTag = InMetaTag;
		}
	}
}


void USimplygonBPFunctionLibrary::RemoveSimplygonUserData( AActor* InActor )
{
	if( !InActor || !InActor->IsA<AStaticMeshActor>() )
		return;

	TInlineComponentArray<UStaticMeshComponent*> Components;
	InActor->GetComponents( Components );

	for( auto& Component : Components )
	{
		USimplygonAssetUserData* SimplygonUserData = Component->GetAssetUserData<USimplygonAssetUserData>();
		if( SimplygonUserData ) 
		{
			Component->RemoveUserDataOfClass( USimplygonAssetUserData::StaticClass() );
		}
	}
	
}

void USimplygonBPFunctionLibrary::SetLODRecipeAllowMorphTargets(ULODRecipe* Recipe, bool bAllowMorphLOD1, bool bAllowMorphLOD2, bool bAllowMorphLOD3, bool bAllowMorphLOD4)
{
	if (Recipe != nullptr)
	{
		const int LODMiscSettingNum = Recipe->PerLODMiscSettings.Num();
		
		if (bAllowMorphLOD1 && LODMiscSettingNum >= 1)
		{
			Recipe->PerLODMiscSettings[1].bApplyMorphTargets = bAllowMorphLOD1;
		}
		
		if (bAllowMorphLOD2 && LODMiscSettingNum >= 2)
		{
			Recipe->PerLODMiscSettings[2].bApplyMorphTargets = bAllowMorphLOD2;
		}
		
		if (bAllowMorphLOD3 && LODMiscSettingNum >= 3)
		{
			Recipe->PerLODMiscSettings[3].bApplyMorphTargets = bAllowMorphLOD3;
		}
		
		if (bAllowMorphLOD4 && LODMiscSettingNum >= 4)
		{
			Recipe->PerLODMiscSettings[4].bApplyMorphTargets = bAllowMorphLOD4;
		}
	}
}

void USimplygonBPFunctionLibrary::SetLODRecipeReductionSetting(ULODRecipe* Recipe, int NumLODs, bool bSetReductionLOD1, bool bSetReductionLOD2, bool bSetReductionLOD3, bool bSetReductionLOD4, FReductionSettings LOD1Settings, FReductionSettings LOD2Settings, FReductionSettings LOD3Settings, FReductionSettings LOD4Settings)
{
	if (Recipe != nullptr)
	{
		Recipe->SetNumLODs(NumLODs);
		
		if (NumLODs >= 2 && bSetReductionLOD1)
		{
			USimplygonPipeline* SimplygonPipeline = Recipe->PerLODPipelineSettings[1];
			if (SimplygonPipeline != nullptr)
			{
				if (UReductionPipeline* ReductionPipeline = Cast<UReductionPipeline>(SimplygonPipeline))
				{
					ReductionPipeline->Settings.ReductionSettings = LOD1Settings;
				}
			}
		}

		if (NumLODs >= 3 && bSetReductionLOD2)
		{
			USimplygonPipeline* SimplygonPipeline = Recipe->PerLODPipelineSettings[2];
			if (SimplygonPipeline != nullptr)
			{
				if (UReductionPipeline* ReductionPipeline = Cast<UReductionPipeline>(SimplygonPipeline))
				{
					ReductionPipeline->Settings.ReductionSettings = LOD2Settings;
				}
			}
		}
	
		if (NumLODs >= 4 && bSetReductionLOD3)
		{
			USimplygonPipeline* SimplygonPipeline = Recipe->PerLODPipelineSettings[3];
			if (SimplygonPipeline != nullptr)
			{
				if(UReductionPipeline* ReductionPipeline = Cast<UReductionPipeline>(SimplygonPipeline))
				{
					ReductionPipeline->Settings.ReductionSettings = LOD3Settings;
				}
			}
		}

		if (NumLODs >= 5 && bSetReductionLOD4)
		{
			USimplygonPipeline* SimplygonPipeline = Recipe->PerLODPipelineSettings[4];
			if (SimplygonPipeline != nullptr)
			{
				if(UReductionPipeline* ReductionPipeline = Cast<UReductionPipeline>(SimplygonPipeline))
				{
					ReductionPipeline->Settings.ReductionSettings = LOD4Settings;
				}
			}
		}

		Recipe->PostEditChange();
	}
}

#undef LOCTEXT_NAMESPACE